﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Game2048WithCSharp
{
    /// <summary>
    /// 大部分函数由Comate AI生成，生成后修改得到
    /// </summary>
    public class LogicController: ILogic
    {
        const int BOARD_SIZE = 4;
        int currentScore;
        int bestScore = 0;
        Random rand = new Random();
        public int[,] board = new int[BOARD_SIZE, BOARD_SIZE];
        public bool move = false;
        void showBoard()
        {
            for (int i = 0; i < BOARD_SIZE; i++)
            {
                for (int j = 0; j < BOARD_SIZE; j++)
                    Debug.Write(board[i, j] + " ");
                Debug.WriteLine("");
            }
        }
        public int[,] NewGame(out int currentScore, out int bestScore)
        {
            BestScoreIO();
            currentScore = 0;
            this.currentScore = 0;
            bestScore = this.bestScore;
            for (int i = 0; i < BOARD_SIZE; ++i)
                for (int j = 0; j < BOARD_SIZE; ++j)
                    board[i, j] = 0;
            GameState gameState;
            move = true;
            GenerateNewCube(out gameState, true);
            GenerateNewCube(out gameState, true);
            move = false;
            return board;
        }
        public CubeMove[] MoveCubes(Direction direction, out GameState gameState, out int currentScore, out int bestScore, out bool _move)
        {
            currentScore = this.currentScore;
            bestScore = this.bestScore;
            Debug.WriteLine(direction);
            Debug.WriteLine("Before Move Cubes");
            showBoard();
            bool[,] changed = new bool[BOARD_SIZE, BOARD_SIZE];
            for (int i = 0;i<BOARD_SIZE; ++i) 
                for (int j = 0;j < BOARD_SIZE; ++j)
                    changed[i, j] = false;
            int startX = 0, startY = 0;
            int endX = 0, endY = 0;
            int deltaX = 1, deltaY = 1;
            switch (direction)
            {
                case Direction.Up: startX = 0; startY = 0; endX = BOARD_SIZE; endY = BOARD_SIZE; deltaX = 1; deltaY = 1; break;
                case Direction.Down: startX = BOARD_SIZE - 1; startY = 0; endX = -1; endY = BOARD_SIZE; deltaX = -1; deltaY = 1; break;
                case Direction.Left: startX = 0; startY = 0; endX = BOARD_SIZE; endY = BOARD_SIZE; deltaX = 1; deltaY = 1; break;
                case Direction.Right: startX = 0; startY = BOARD_SIZE - 1; endX = BOARD_SIZE; endY = -1; deltaX = 1; deltaY = -1; break;
            }

            List<CubeMove> res_list = new List<CubeMove>();

            for (int j = startY; j != endY; j += deltaY)
            {
                for (int i = startX; i != endX; i += deltaX)
                {
                    if (board[i, j] == 0) continue;
                    int next = findNextNotEmpty(i, j, direction);
                    if (next / BOARD_SIZE == i && next % BOARD_SIZE == j)
                        continue;
                    if (board[next / BOARD_SIZE, next % BOARD_SIZE] == 0)
                    {
                        CubeMove move = new CubeMove();
                        move.OldX = i; move.OldY = j;
                        move.NewX = next / BOARD_SIZE; move.NewY = next % BOARD_SIZE;
                        res_list.Add(move);

                        board[next / BOARD_SIZE, next % BOARD_SIZE] = board[i, j];
                        board[i, j] = 0;
                    }
                    else
                    {
                        if (board[next / BOARD_SIZE, next % BOARD_SIZE] == board[i, j])
                        {
                            if (!changed[next / BOARD_SIZE, next % BOARD_SIZE])
                            {
                                CubeMove move = new CubeMove();
                                move.OldX = i; move.OldY = j;
                                move.NewX = next / BOARD_SIZE; move.NewY = next % BOARD_SIZE;
                                res_list.Add(move);

                                this.currentScore += board[i, j] * 2;
                                this.bestScore = Math.Max(this.currentScore, this.bestScore);
                                board[next / BOARD_SIZE, next % BOARD_SIZE] *= 2;
                                board[i, j] = 0;
                                changed[next / BOARD_SIZE, next % BOARD_SIZE] = true;
                            }
                            else
                            {
                                next = findNextEmpty(i, j, direction);
                                if (next == -1)
                                    continue;
                                if (next / BOARD_SIZE != i || next % BOARD_SIZE != j)
                                {
                                    CubeMove move = new CubeMove();
                                    move.OldX = i; move.OldY = j;
                                    move.NewX = next / BOARD_SIZE; move.NewY = next % BOARD_SIZE;
                                    res_list.Add(move);

                                    board[next / BOARD_SIZE, next % BOARD_SIZE] = board[i, j];
                                    board[i, j] = 0;
                                }
                            }
                        }
                        else
                        {
                            next = findNextEmpty(i, j, direction);
                            if (next == -1)
                                continue;
                            if (next / BOARD_SIZE != i || next % BOARD_SIZE != j)
                            {
                                CubeMove move = new CubeMove();
                                move.OldX = i; move.OldY = j;
                                move.NewX = next / BOARD_SIZE; move.NewY = next % BOARD_SIZE;
                                res_list.Add(move);

                                board[next / BOARD_SIZE, next % BOARD_SIZE] = board[i, j];
                                board[i, j] = 0;
                            }
                        }
                    }
                }
            }
            if (this.bestScore > bestScore)
                BestScoreIO();
            currentScore = this.currentScore;
            bestScore = this.bestScore;
            move = res_list.Count != 0;
            gameState = CheckGameState();
            Debug.WriteLine("Move Cubes");
            showBoard();
            _move = move;
            return res_list.ToArray();
        }
        GameState CheckGameState()
        {
            for (int i = 0; i < BOARD_SIZE; i++)
            {
                for (int j = 0; j < BOARD_SIZE; j++)
                {
                    if (board[i, j] == 2048)
                    {
                        return GameState.Victory;
                    }

                }
            }

            for (int i = 0; i < BOARD_SIZE; i++)
            {
                for (int j = 0; j < BOARD_SIZE; j++)
                {
                    if (board[i, j] == 0)
                    {
                        return GameState.Gaming;
                    }
                    if (i < BOARD_SIZE - 1 && board[i, j] == board[i + 1, j])
                    {
                        return GameState.Gaming;
                    }
                    if (j < BOARD_SIZE - 1 && board[i, j] == board[i, j + 1])
                    {
                        return GameState.Gaming;
                    }
                }
            }
            return GameState.Defeat;
        }
        public int[,] GenerateNewCube(out GameState gameState, bool _move)
        {
            int[,] res = new int[BOARD_SIZE, BOARD_SIZE];
            for (int i = 0; i < BOARD_SIZE; ++i)
                for (int j = 0; j < BOARD_SIZE; ++j)
                    res[i, j] = 0;
            if (!move) { gameState = GameState.Gaming; return res; }
            int pos = rand.Next(BOARD_SIZE * BOARD_SIZE);
            int new_x = 0;
            int new_y = 0;
            int new_v = 0;
            for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; ++i)
            {
                if (board[pos / BOARD_SIZE, pos % BOARD_SIZE] == 0)
                {
                    new_x = pos / BOARD_SIZE;
                    new_y = pos % BOARD_SIZE;
                    new_v = (rand.Next(10) == 0) ? 4 : 2;
                    board[pos / BOARD_SIZE, pos % BOARD_SIZE] = new_v;
                    break;
                }
                else pos = (pos + 1) % (BOARD_SIZE * BOARD_SIZE);
            }
            res[new_x, new_y] = new_v;
            Debug.WriteLine("Generate New Cube");
            gameState = CheckGameState();
            Debug.WriteLine(gameState);
            showBoard();
            return res;
        }
        int findNextNotEmpty(int x, int y, Direction action)
        {
            switch (action)
            {
                case Direction.Up:
                    for (int i = x - 1; i >= 0; --i)
                    {
                        if (board[i, y] != 0) return i * BOARD_SIZE + y;
                    }
                    return y;
                case Direction.Down:
                    for (int i = x + 1; i < BOARD_SIZE; ++i)
                    {
                        if (board[i, y] != 0) return i * BOARD_SIZE + y;
                    }
                    return (BOARD_SIZE - 1) * BOARD_SIZE + y;
                case Direction.Left:
                    for (int j = y - 1; j >= 0; --j)
                    {
                        if (board[x, j] != 0) return x * BOARD_SIZE + j;
                    }
                    return x * BOARD_SIZE;
                case Direction.Right:
                    for (int j = y + 1; j < BOARD_SIZE; ++j)
                    {
                        if (board[x, j] != 0) return x * BOARD_SIZE + j;
                    }
                    return x * BOARD_SIZE + BOARD_SIZE - 1;
                default:
                    return -1;
            }
        }
        int findNextEmpty(int x, int y, Direction action)
        {
            switch (action)
            {
                case Direction.Up:
                    for (int i = x - 1; i >= 0; --i)
                    {
                        if (board[i, y] != 0) return (i + 1) * BOARD_SIZE + y;
                    }
                    break;
                case Direction.Down:
                    for (int i = x + 1; i < BOARD_SIZE; ++i)
                    {
                        if (board[i, y] != 0) return (i - 1) * BOARD_SIZE + y;
                    }
                    break;
                case Direction.Left:
                    for (int j = y - 1; j >= 0; --j)
                    {
                        if (board[x, j] != 0) return x * BOARD_SIZE + (j + 1);
                    }
                    break;
                case Direction.Right:
                    for (int j = y + 1; j < BOARD_SIZE; ++j)
                    {
                        if (board[x, j] != 0) return x * BOARD_SIZE + (j - 1);
                    }
                    break;
            }
            return -1;
        }
        void BestScoreIO()
        {
            string filePath = Application.StartupPath;
            filePath = Path.Combine(filePath, "BestScoreRecord.record");
            if (!File.Exists(filePath))
            {
                FileStream fs = File.Create(filePath);
                fs.WriteByte((byte)bestScore);
                fs.Close();
            } 
            else
            {
                FileStream fs = File.OpenRead(filePath);
                int scoreBest = fs.ReadByte();
                bestScore = Math.Max(bestScore, scoreBest);
                fs.Close();
                fs = File.OpenWrite(filePath);
                fs.WriteByte((byte)bestScore);
                fs.Close();
            }
            return;
        }
    }
}
